﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Reflection;

namespace SongSharp.Domain.Suggesting.VaryVogueValue
{        
    public class RulesManager
    {
        public static class Helper
        {
            public static void Scale(ref ChanceValue[] chances) 
            {
                double[] values = chances.
                    Select(c => c.Value).
                    ToArray();

                double sum = values.Sum();

                foreach (ChanceValue c in chances)
                    c.Value /= sum;
            }
        }

        class ClientInfo
        {
            public double[] RuleSet { get; private set; }
            public double DegreeOfBelief { get; private set; }

            public ClientInfo(double[] ruleSet, double degreeOfBelief)
            {
                RuleSet = ruleSet;
                DegreeOfBelief = degreeOfBelief;
            }
        }

        // Property is just used to access reflection information
        private SpectrumElement ReflectionReference { get; set; }

        private static RulesManager ms_instance;
        private Hashtable m_clients;
        private int m_boundaryCount;

        private RulesManager()
        {
            m_clients = new Hashtable();
            m_boundaryCount = ReflectionReference.GetType().GetFields(BindingFlags.Public | BindingFlags.Static).Length;  
        }

        public static RulesManager Instance
        {
            get  {
                if (ms_instance == null)
                    ms_instance = new RulesManager();
                return ms_instance; 
            }
        }

        /// <summary>
        /// Throws an ArgumentException if the rule array length does not match the number of enum elements in the type 
        /// SpectrumElement. When comparing it's m_boundaryCount - 2 because of the vacant element "Undefined" and the fact
        /// that the enum types are "shifted between" the values of the array.
        /// </summary>
        /// <param name="rules"></param>
        public void RegisterRuleSet(Hashtable rules)
        {
            foreach (object key in rules.Keys)
            {
                if (((RuleSet)rules[key]).Rules.Length != m_boundaryCount - 2)
                    throw new ArgumentException("The ruleset must qualify the amount of elements in the enumeration 'Spectrum.'");
                else
                    m_clients[key] =  rules[key];
            }
        }

        public RuleSet GetRuleSet(object key)
        {
            if (m_clients.ContainsKey(key))
            {
                return ((RuleSet)m_clients[key]);
            } 
            return null;
        }

        public ChanceValue CreateInterpretation(object key, double value)
        {
            int currentBoundary = 1;
            double[] boundary = GetRuleSet(key).Rules;
            bool reversed = false;
            double scaledValue = double.NaN;
            SpectrumElement scaledType = SpectrumElement.Undefined;
            ChanceValue chanceValue = ChanceValue.UNDEFINED;

            if (boundary[0] > boundary[boundary.Length - 1])
            {
                boundary = boundary.Reverse().ToArray();
                reversed = true;
            }

            if ((value < boundary[0] && !reversed) || (value >= boundary[boundary.Length - 1] && reversed))
            {
                // value is below any bounds
                scaledValue = 0.0;
                scaledType = SpectrumElement.Worst;
            }
            else if ((value >= boundary[boundary.Length - 1] && !reversed) || (value < boundary[0] && reversed))
            {
                scaledValue = 1.0;
                scaledType = SpectrumElement.Best;
                // value is above any bounds
            }
            else
            {
                int i = 0;
                foreach (double d in boundary)
                {
                    if (i + 1 < boundary.Length)
                    {
                        // if value is between arr[i] and arr[i+1]
                        if (d <= value && value < boundary[i + 1])
                        {
                            double scaledLowerBound = (currentBoundary / (m_boundaryCount - 2.0));
                            currentBoundary++;
                            double scaledUpperBound = (currentBoundary / (m_boundaryCount - 2.0));

                            scaledValue = ((scaledUpperBound - scaledLowerBound) / (boundary[i + 1] - d))
                                * (value - d) + scaledLowerBound;

                            if (reversed)
                            {
                                scaledValue = 1.0 - scaledValue;
                                currentBoundary = m_boundaryCount - currentBoundary;
                            }
                            
                            scaledType = (SpectrumElement)Enum.ToObject(typeof(SpectrumElement), currentBoundary);
                            break;
                        }
                    }
                    currentBoundary++;
                    i++;
                }
            }
            chanceValue = new ChanceValue(scaledValue, value, GetRuleSet(key).DegreeOfBelief, scaledType);
            return chanceValue;
        }
    }
}
